package crontab

import (
	"context"
	"encoding/json"
	"gitee.com/Luna-CY/hui-hui/internal/logger"
	"gitee.com/Luna-CY/hui-hui/internal/runtime"
	"gitee.com/Luna-CY/hui-hui/internal/util/syscall"
	"github.com/robfig/cron/v3"
	"os"
	"path/filepath"
	"sync"
)

var config = filepath.Join(runtime.ConfigRoot, "crontab.json")

var once sync.Once
var instance *Crontab

func Single() *Crontab {
	once.Do(func() {
		instance = &Crontab{cron: cron.New(cron.WithSeconds())}

		if err := os.MkdirAll(runtime.ConfigRoot, 0755); nil != err {
			logger.GetLogger().Sugar().Errorf("初始化配置目录失败，失败原因: %s", err)

			os.Exit(1)
		}

		f, err := os.Open(config)
		if nil != err && !os.IsNotExist(err) {
			logger.GetLogger().Sugar().Errorf("读取定时器任务表失败，失败原因: %s", err)

			os.Exit(1)
		}

		if nil != f {
			defer func() {
				if err := f.Close(); nil != err {
					logger.GetLogger().Sugar().Errorf("关闭任务表文件失败，失败原因: %s", err)
				}
			}()

			if err := json.NewDecoder(f).Decode(&instance.user); nil != err {
				logger.GetLogger().Sugar().Errorf("读取定时器任务表数据失败，失败原因: %s", err)

				os.Exit(1)
			}

			for _, task := range instance.user {
				id, err := instance.cron.AddFunc(task.Schedule, func() {
					if _, err := syscall.ExecuteCommandGetOutputIgnoreQuitCode(context.Background(), task.Command, task.Args...); nil != err {
						logger.GetLogger().Sugar().Errorf("执行定时任务失败，失败原因: %s，任务ID: %s", err, task.Id)
					}
				})

				if nil != err {
					logger.GetLogger().Sugar().Errorf("设置定时任务失败，失败原因: %s，任务ID: %s", err, task.Id)

					continue
				}

				task.EntryId = id
			}
		}

		instance.cron.Start()
		logger.GetLogger().Sugar().Info("定时器已启动")
	})

	return instance
}

// Crontab Crontab定时任务管理器
type Crontab struct {
	cron *cron.Cron

	mutex  sync.RWMutex // 资源锁
	system []*Task      // 系统任务，此任务表不会持久化到磁盘
	user   []*Task      // 用户任务，此任务表会持久化到磁盘
}

// sync 同步用户任务表到磁盘
// 调用此方法前，调用方必须取得资源锁
func (cls *Crontab) sync() error {
	f, err := os.OpenFile(config, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
	if nil != err {
		return err
	}

	defer func() {
		if err := f.Close(); nil != err {
			logger.GetLogger().Sugar().Errorf("关闭任务表文件失败，失败原因: %s", err)
		}
	}()

	if err := json.NewEncoder(f).Encode(cls.user); nil != err {
		return err
	}

	return nil
}

type Task struct {
	Id       string   `json:"id"`       // 任务唯一ID
	Schedule string   `json:"schedule"` // 调度规则
	Command  string   `json:"command"`  // 执行的命令
	Args     []string `json:"args"`     // 参数表

	EntryId  cron.EntryID `json:"-"` // cron的内部ID
	Callback func()       `json:"-"` // 内部调用函数，不可持久化，仅系统任务有效
}
